/*
* 写者优先
* 当有写者等待时，新到达的读者必须等待，直到所有写者完成。
*/
#include <iostream>
using namespace std;

#include <thread>
#include <semaphore>//需要开启 C++ 20 的支持
#include <mutex>

#include <chrono>


uint8_t resource[1024 * 1024] = { 0 };//共享的资源，这里以一块内存为例

/**
 * @brief 保护共享资源的二值信号量
 * @note 多个读者可以同时读，读者a获得后可能由读者b释放，因此必须使用信号量
 */
binary_semaphore resource_semaphore(1);

int readers_count = 0;//正在读取的读者数量
mutex readers_count_mutex;//保护读者数量的互斥锁（使用原子操作 atomic 也可以）

/**
 * @brief 正在写入以及等待写入的写者数量
 * @note 注意，是正在写和等着写的写者总数
 */
int writers_count = 0;
mutex writing_mutex;//保护正在写的互斥锁（使用原子操作 atomic 也可以）


binary_semaphore read_allow_semaphore(1);//是否允许读取的信号量


/**
 * @brief 读者线程的函数
 * @param readerNumer 读者编号 调试用
 */
void reader_function(int readerNumer)
{
    cout << "读者线程 " << readerNumer << "建立  线程id " << this_thread::get_id() << endl;

    while (true)
    {
        /* 读者读取之前应当执行的内容 */
        read_allow_semaphore.acquire();//等待直到所有写者完毕
        readers_count_mutex.lock();//加锁，保护读者计数
        if (readers_count == 0)//如果是第一个读者
        {
            resource_semaphore.acquire();//获取资源信号量
        }
        readers_count++;//读者数量增加
        readers_count_mutex.unlock();//解锁
        read_allow_semaphore.release();

        /* 读者读取 */
        ////当比较大时，如果放到栈(stack)区，会出现 栈溢出 的错误
        //uint8_t readerBuffer[1024 * 1024] = { 0 };
        uint8_t* readerBuffer = new uint8_t[1024 * 1024];//比较大需要放到堆(heap)中
        memcpy(readerBuffer, resource, sizeof(resource));//读取
        cout << "读者 " << readerNumer << " 读取到内容的前8个字节" << endl;
        for (int i = 0; i < 8; i++)
        {
            printf("%#X ", readerBuffer[i]);
        }
        cout << endl;
        delete[] readerBuffer;
        this_thread::sleep_for(chrono::milliseconds(5));//延时，模拟对读取到的数据进行某些处理

        /* 读者读取完毕应当执行的内容 */
        readers_count_mutex.lock();//加锁，保护读者计数        
        readers_count--;//读者数量减少
        if (readers_count == 0)//如果是最后一个读者
        {
            resource_semaphore.release();//释放资源信号量
        }
        readers_count_mutex.unlock();//解锁

        this_thread::sleep_for(chrono::milliseconds(1));//延时
    }
}


/**
 * @brief 写者线程
 * @param writerNumber 写者编号 调试用
 */
void writer_function(int writerNumber)
{
    cout << "写者线程 " << writerNumber << "建立  线程id " << this_thread::get_id() << endl;
    static int count = 0;//调试用

    while (true)
    {
        /* 写者写入之前应当执行的内容 */
        writing_mutex.lock();
        if (writers_count == 0)//第一个写入或等待的写者
        {
            read_allow_semaphore.acquire();//阻塞读者  
        }
        writers_count++;//正在写入或者等待的读者数量自增         
        writing_mutex.unlock();
        resource_semaphore.acquire();//获取资源信号量

        /* 写者写入 */
        count++;
        for (int i = 0; i < sizeof(resource); i++)//写入数据
        {
            resource[i] = (i + count) % 256;
        }
        cout << "写者 " << writerNumber << " 写入内容的前8个字节是" << endl;
        for (int i = 0; i < 8; i++)
        {
            printf("%#X ", resource[i]);
        }
        cout << endl;
        this_thread::sleep_for(chrono::milliseconds(20));//延时，模拟大量数据写入的时间消耗

        /* 写者写入完毕应当执行的内容 */
        resource_semaphore.release();//释放资源信号量 
        writing_mutex.lock();
        writers_count--;
        if (writers_count == 0)//如果不再有正在写入或等待的写者
        {
            read_allow_semaphore.release();//允许读者 
        }               
        writing_mutex.unlock();

        this_thread::sleep_for(chrono::milliseconds(85));//延时

        /**
        * @note 当这个延时小于所有写者写入时间总和，就可以模拟“写者源源不断到来”
        * @note 对于写者优先，写者源源不断到来，则读者饥饿
        */
        //this_thread::sleep_for(chrono::milliseconds(75));//延时
    }
}


int main()
{
    std::cout << "开始测试\n";
    std::thread readers[5], writers[4];
    for (int i = 0; i < 5; ++i) readers[i] = std::thread(reader_function, i);
    for (int i = 0; i < 4; ++i) writers[i] = std::thread(writer_function, i);

    for (auto& t : readers) t.join();
    for (auto& t : writers) t.join();

    return 0;
}